// ==========================================================
// Photoshop Loader
//
// Design and implementation by
// - Hervé Drolon (drolon@infonie.fr)
// - Mihail Naydenov (mnaydenov@users.sourceforge.net)
//
// Based on LGPL code created and published by http://sourceforge.net/projects/elynx/
//
// This file is part of FreeImage 3
//
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
// THIS DISCLAIMER.
//
// Use at your own risk!
// ==========================================================

#include "FreeImage.h"
#include "Utilities.h"
#include "PSDParser.h"

// --------------------------------------------------------------------------

// PSD signature (= '8BPS')
#define PSD_SIGNATURE   0x38425053
// Image resource block signature (= '8BIM')
#define PSD_RESOURCE    0x3842494D

// PSD color modes
#define PSDP_BITMAP         0
#define PSDP_GRAYSCALE      1
#define PSDP_INDEXED        2
#define PSDP_RGB            3
#define PSDP_CMYK           4
#define PSDP_MULTICHANNEL   7
#define PSDP_DUOTONE        8
#define PSDP_LAB            9

// PSD compression schemes
#define PSDP_COMPRESSION_NONE   0   // Raw data
#define PSDP_COMPRESSION_RLE    1   // RLE compression (same as TIFF packed bits)

#define SAFE_DELETE_ARRAY(_p_) { if (NULL != (_p_)) { delete [] (_p_); (_p_) = NULL; } }

// --------------------------------------------------------------------------

static inline int
psdGetValue(const BYTE * iprBuffer, const int iBytes) {
    int v = iprBuffer[0];
    for (int i=1; i<iBytes; ++i) {
        v = (v << 8) | iprBuffer[i];
    }
    return v;
}

// --------------------------------------------------------------------------

psdHeaderInfo::psdHeaderInfo() : _Channels(-1), _Height(-1), _Width(-1), _BitsPerChannel(-1), _ColourMode(-1) {
}

psdHeaderInfo::~psdHeaderInfo() {
}

bool psdHeaderInfo::Read(FreeImageIO *io, fi_handle handle) {
    psdHeader header;

    const int n = (int)io->read_proc(&header, sizeof(header), 1, handle);
    if(!n) {
        return false;
    }

    // check the signature
    int nSignature = psdGetValue(header.Signature, sizeof(header.Signature));
    if (PSD_SIGNATURE == nSignature) {
        // check the version
        int nVersion = psdGetValue( header.Version, sizeof(header.Version) );
        if (1 == nVersion) {
            // header.Reserved must be zero
            BYTE psd_reserved[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
            if(memcmp(header.Reserved, psd_reserved, 6) != 0) {
                FreeImage_OutputMessageProc(FIF_PSD, "Warning: file header reserved member is not equal to zero");
            }
            // read the header
            _Channels = (short)psdGetValue( header.Channels, sizeof(header.Channels) );
            _Height = psdGetValue( header.Rows, sizeof(header.Rows) );
            _Width = psdGetValue( header.Columns, sizeof(header.Columns) );
            _BitsPerChannel = (short)psdGetValue( header.Depth, sizeof(header.Depth) );
            _ColourMode = (short)psdGetValue( header.Mode, sizeof(header.Mode) );

            return true;
        }
    }

    return false;
}

// --------------------------------------------------------------------------

psdColourModeData::psdColourModeData() : _Length(-1), _plColourData(NULL) {
}

psdColourModeData::~psdColourModeData() {
    SAFE_DELETE_ARRAY(_plColourData);
}

bool psdColourModeData::Read(FreeImageIO *io, fi_handle handle) {
    if (0 < _Length) {
        SAFE_DELETE_ARRAY(_plColourData);
    }

    BYTE Length[4];
    io->read_proc(&Length, sizeof(Length), 1, handle);

    _Length = psdGetValue( Length, sizeof(_Length) );
    if (0 < _Length) {
        _plColourData = new BYTE[_Length];
        io->read_proc(_plColourData, _Length, 1, handle);
    }

    return true;
}

bool psdColourModeData::FillPalette(FIBITMAP *dib) {
    RGBQUAD *pal = FreeImage_GetPalette(dib);
    if(pal) {
        for (int i = 0; i < 256; i++) {
            pal[i].rgbRed   = _plColourData[i + 0*256];
            pal[i].rgbGreen = _plColourData[i + 1*256];
            pal[i].rgbBlue  = _plColourData[i + 2*256];
        }
        return true;
    }
    return false;
}

// --------------------------------------------------------------------------

psdImageResource::psdImageResource() : _plName (0) {
    Reset();
}

psdImageResource::~psdImageResource() {
    SAFE_DELETE_ARRAY(_plName);
}

void psdImageResource::Reset() {
    _Length = -1;
    memset( _OSType, '\0', sizeof(_OSType) );
    _ID = -1;
    SAFE_DELETE_ARRAY(_plName);
    _Size = -1;
}

// --------------------------------------------------------------------------

psdResolutionInfo::psdResolutionInfo() : _widthUnit(-1), _heightUnit(-1), _hRes(-1), _vRes(-1), _hResUnit(-1), _vResUnit(-1) {
}

psdResolutionInfo::~psdResolutionInfo() {
}

int psdResolutionInfo::Read(FreeImageIO *io, fi_handle handle) {
    BYTE IntValue[4], ShortValue[2];
    int nBytes=0, n;

    // Horizontal resolution in pixels per inch.
    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _hRes = (short)psdGetValue(ShortValue, sizeof(_hRes) );
    // 1=display horizontal resolution in pixels per inch; 2=display horizontal resolution in pixels per cm.
    n = (int)io->read_proc(&IntValue, sizeof(IntValue), 1, handle);
    nBytes += n * sizeof(IntValue);
    _hResUnit = psdGetValue(IntValue, sizeof(_hResUnit) );
    // Display width as 1=inches; 2=cm; 3=points; 4=picas; 5=columns.
    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _widthUnit = (short)psdGetValue(ShortValue, sizeof(_widthUnit) );
    // Vertical resolution in pixels per inch.
    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _vRes = (short)psdGetValue(ShortValue, sizeof(_vRes) );
    // 1=display vertical resolution in pixels per inch; 2=display vertical resolution in pixels per cm.
    n = (int)io->read_proc(&IntValue, sizeof(IntValue), 1, handle);
    nBytes += n * sizeof(IntValue);
    _vResUnit = psdGetValue(IntValue, sizeof(_vResUnit) );
    // Display height as 1=inches; 2=cm; 3=points; 4=picas; 5=columns.
    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _heightUnit = (short)psdGetValue(ShortValue, sizeof(_heightUnit) );

    return nBytes;
}

void psdResolutionInfo::GetResolutionInfo(unsigned &res_x, unsigned &res_y) {
    if(_hResUnit == 1) {
        // convert pixels / inch to pixel / m
        res_x = (unsigned) (_hRes / 0.0254000 + 0.5);
    } else if(_hResUnit == 2) {
        // convert pixels / cm to pixel / m
        res_x = (unsigned) (_hRes * 100.0 + 0.5);
    }
    if(_vResUnit == 1) {
        // convert pixels / inch to pixel / m
        res_y = (unsigned) (_vRes / 0.0254000 + 0.5);
    } else if(_vResUnit == 2) {
        // convert pixels / cm to pixel / m
        res_y = (unsigned) (_vRes * 100.0 + 0.5);
    }
}

// --------------------------------------------------------------------------

psdResolutionInfo_v2::psdResolutionInfo_v2() {
    _Channels = _Rows = _Columns = _Depth = _Mode = -1;
}

psdResolutionInfo_v2::~psdResolutionInfo_v2() {
}

int psdResolutionInfo_v2::Read(FreeImageIO *io, fi_handle handle) {
    BYTE ShortValue[2];
    int nBytes=0, n;

    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _Channels = (short)psdGetValue(ShortValue, sizeof(_Channels) );

    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _Rows = (short)psdGetValue(ShortValue, sizeof(_Rows) );

    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _Columns = (short)psdGetValue(ShortValue, sizeof(_Columns) );

    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _Depth = (short)psdGetValue(ShortValue, sizeof(_Depth) );

    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _Mode = (short)psdGetValue(ShortValue, sizeof(_Mode) );

    return nBytes;
}

// --------------------------------------------------------------------------

psdDisplayInfo::psdDisplayInfo() {
    _Opacity = _ColourSpace = -1;
    for (unsigned n = 0; n < 4; ++n) {
        _Colour[n] = 0;
    }
    _Kind = 0;
    _padding = '0';
}

psdDisplayInfo::~psdDisplayInfo() {
}

int psdDisplayInfo::Read(FreeImageIO *io, fi_handle handle) {
    BYTE ShortValue[2];
    int nBytes=0, n;

    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _ColourSpace = (short)psdGetValue(ShortValue, sizeof(_ColourSpace) );

    for (unsigned i = 0; i < 4; ++i) {
        n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
        nBytes += n * sizeof(ShortValue);
        _Colour[i] = (short)psdGetValue(ShortValue, sizeof(_Colour[i]) );
    }

    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _Opacity = (short)psdGetValue(ShortValue, sizeof(_Opacity) );
    if((_Opacity < 0) || (_Opacity > 100)) {
        throw "Invalid DisplayInfo::Opacity value";
    }

    BYTE c[1];
    n = (int)io->read_proc(&c, sizeof(c), 1, handle);
    nBytes += n * sizeof(c);
    _Kind = (BYTE)psdGetValue(c, sizeof(c));

    n = (int)io->read_proc(&c, sizeof(c), 1, handle);
    nBytes += n * sizeof(c);
    _padding = (BYTE)psdGetValue(c, sizeof(c));
    if(_padding != 0) {
        throw "Invalid DisplayInfo::Padding value";
    }

    return nBytes;
}

// --------------------------------------------------------------------------

psdThumbnail::psdThumbnail() :
        _Format(-1), _Width(-1), _Height(-1), _WidthBytes(-1), _Size(-1), _CompressedSize(-1), _BitPerPixel(-1), _Planes(-1), _dib(NULL) {
}

psdThumbnail::~psdThumbnail() {
    FreeImage_Unload(_dib);
}

int psdThumbnail::Read(FreeImageIO *io, fi_handle handle, int iResourceSize, bool isBGR) {
    BYTE ShortValue[2], IntValue[4];
    int nBytes=0, n;

    // remove the header size (28 bytes) from the total data size
    int iTotalData = iResourceSize - 28;

    const long block_end = io->tell_proc(handle) + iTotalData;

    n = (int)io->read_proc(&IntValue, sizeof(IntValue), 1, handle);
    nBytes += n * sizeof(IntValue);
    _Format = psdGetValue(IntValue, sizeof(_Format) );

    n = (int)io->read_proc(&IntValue, sizeof(IntValue), 1, handle);
    nBytes += n * sizeof(IntValue);
    _Width = psdGetValue(IntValue, sizeof(_Width) );

    n = (int)io->read_proc(&IntValue, sizeof(IntValue), 1, handle);
    nBytes += n * sizeof(IntValue);
    _Height = psdGetValue(IntValue, sizeof(_Height) );

    n = (int)io->read_proc(&IntValue, sizeof(IntValue), 1, handle);
    nBytes += n * sizeof(IntValue);
    _WidthBytes = psdGetValue(IntValue, sizeof(_WidthBytes) );

    n = (int)io->read_proc(&IntValue, sizeof(IntValue), 1, handle);
    nBytes += n * sizeof(IntValue);
    _Size = psdGetValue(IntValue, sizeof(_Size) );

    n = (int)io->read_proc(&IntValue, sizeof(IntValue), 1, handle);
    nBytes += n * sizeof(IntValue);
    _CompressedSize = psdGetValue(IntValue, sizeof(_CompressedSize) );

    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _BitPerPixel = (short)psdGetValue(ShortValue, sizeof(_BitPerPixel) );

    n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
    nBytes += n * sizeof(ShortValue);
    _Planes = (short)psdGetValue(ShortValue, sizeof(_Planes) );

    const long JFIF_startpos = io->tell_proc(handle);

    if(_dib) {
        FreeImage_Unload(_dib);
    }

    if(_Format == 1) {
        // kJpegRGB thumbnail image
        _dib = FreeImage_LoadFromHandle(FIF_JPEG, io, handle);
        if(isBGR) {
            SwapRedBlue32(_dib);
        }
        // HACK: manually go to end of thumbnail, because (for some reason) LoadFromHandle consumes more bytes then available!
        io->seek_proc(handle, block_end, SEEK_SET);
    }
    else {
        // kRawRGB thumbnail image
        // ### Unimplemented (should be trivial)

        // skip the thumbnail part
        io->seek_proc(handle, iTotalData, SEEK_CUR);
        return iResourceSize;
    }

    nBytes += (block_end - JFIF_startpos);

    return nBytes;
}

//---------------------------------------------------------------------------

psdICCProfile::psdICCProfile() : _ProfileSize(0), _ProfileData(NULL) {
}

psdICCProfile::~psdICCProfile() {
    clear();
}

void psdICCProfile::clear() { SAFE_DELETE_ARRAY(_ProfileData); _ProfileSize = 0;}

int psdICCProfile::Read(FreeImageIO *io, fi_handle handle, int size) {
    int nBytes = 0, n;

    clear();

    _ProfileData = new (std::nothrow) BYTE[size];
    if(NULL != _ProfileData) {
        n = (int)io->read_proc(_ProfileData, 1, size, handle);
        _ProfileSize = size;
        nBytes += n * sizeof(BYTE);
    }

    return nBytes;
}

//---------------------------------------------------------------------------

/**
   Invert only color components, skipping Alpha/Black
   (Can be useful as public/utility function)
*/
static
BOOL invertColor(FIBITMAP* dib) {
    FREE_IMAGE_TYPE type = FreeImage_GetImageType(dib);
    const unsigned Bpp = FreeImage_GetBPP(dib)/8;

    if((type == FIT_BITMAP && Bpp == 4) || type == FIT_RGBA16) {
        const unsigned width = FreeImage_GetWidth(dib);
        const unsigned height = FreeImage_GetHeight(dib);
        BYTE *line_start = FreeImage_GetScanLine(dib, 0);
        const unsigned pitch = FreeImage_GetPitch(dib);
        const unsigned triBpp = Bpp - (Bpp == 4 ? 1 : 2);

        for(unsigned y = 0; y < height; y++) {
            BYTE *line = line_start;

            for(unsigned x = 0; x < width; x++) {
                for(unsigned b=0; b < triBpp; ++b) {
                    line[b] = ~line[b];
                }

                line += Bpp;
            }
            line_start += pitch;
        }

        return TRUE;
    }
    else {
        return FreeImage_Invert(dib);
    }
}

//---------------------------------------------------------------------------

psdParser::psdParser() {
    _bThumbnailFilled = false;
    _bDisplayInfoFilled = false;
    _bResolutionInfoFilled = false;
    _bResolutionInfoFilled_v2 = false;
    _bCopyright = false;
    _GlobalAngle = 30;
    _ColourCount = -1;
    _TransparentIndex = -1;
    _fi_flags = 0;
    _fi_format_id = FIF_UNKNOWN;
}

psdParser::~psdParser() {
}

bool psdParser::ReadLayerAndMaskInfoSection(FreeImageIO *io, fi_handle handle)  {
    bool bSuccess = false;

    BYTE DataLength[4];
    int nBytes = 0;
    int n = (int)io->read_proc(&DataLength, sizeof(DataLength), 1, handle);
    int nTotalBytes = psdGetValue( DataLength, sizeof(DataLength) );

    BYTE data[1];
    while( n && ( nBytes < nTotalBytes ) ) {
        data[0] = '\0';
        n = (int)io->read_proc(&data, sizeof(data), 1, handle);
        nBytes += n * sizeof(data);
    }

    if ( nBytes == nTotalBytes ) {
        bSuccess = true;
    }

    return bSuccess;
}

bool psdParser::ReadImageResources(FreeImageIO *io, fi_handle handle, LONG length) {
    psdImageResource oResource;
    bool bSuccess = false;

    if(length > 0) {
        oResource._Length = length;
    } else {
        BYTE Length[4];
        int n = (int)io->read_proc(&Length, sizeof(Length), 1, handle);

        oResource._Length = psdGetValue( Length, sizeof(oResource._Length) );
    }

    int nBytes = 0;
    int nTotalBytes = oResource._Length;

    while(nBytes < nTotalBytes) {
        int n = 0;
        oResource.Reset();

        n = (int)io->read_proc(&oResource._OSType, sizeof(oResource._OSType), 1, handle);
        if(n != 1) {
            FreeImage_OutputMessageProc(_fi_format_id, "This file contains damaged data causing an unexpected end-of-file - stop reading resources");
            return false;
        }
        nBytes += n * sizeof(oResource._OSType);

        if( (nBytes % 2) != 0 ) {
            return false;
        }

        int nOSType = psdGetValue((BYTE*)&oResource._OSType, sizeof(oResource._OSType));

        if ( PSD_RESOURCE == nOSType ) {
            BYTE ID[2];
            n = (int)io->read_proc(&ID, sizeof(ID), 1, handle);
            nBytes += n * sizeof(ID);

            oResource._ID = (short)psdGetValue( ID, sizeof(ID) );

            BYTE SizeOfName;
            n = (int)io->read_proc(&SizeOfName, sizeof(SizeOfName), 1, handle);
            nBytes += n * sizeof(SizeOfName);

            int nSizeOfName = psdGetValue( &SizeOfName, sizeof(SizeOfName) );
            if ( 0 < nSizeOfName ) {
                oResource._plName = new BYTE[nSizeOfName];
                n = (int)io->read_proc(oResource._plName, nSizeOfName, 1, handle);
                nBytes += n * nSizeOfName;
            }

            if ( 0 == (nSizeOfName % 2) ) {
                n = (int)io->read_proc(&SizeOfName, sizeof(SizeOfName), 1, handle);
                nBytes += n * sizeof(SizeOfName);
            }

            BYTE Size[4];
            n = (int)io->read_proc(&Size, sizeof(Size), 1, handle);
            nBytes += n * sizeof(Size);

            oResource._Size = psdGetValue( Size, sizeof(oResource._Size) );

            if ( 0 != (oResource._Size % 2) ) {
                // resource data must be even
                oResource._Size++;
            }
            if ( 0 < oResource._Size ) {
                BYTE IntValue[4];
                BYTE ShortValue[2];

                switch( oResource._ID ) {
                    case 1000:
                        // Obsolete - Photoshop 2.0
                        _bResolutionInfoFilled_v2 = true;
                        nBytes += _resolutionInfo_v2.Read(io, handle);
                        break;

                        // ResolutionInfo structure
                    case 1005:
                        _bResolutionInfoFilled = true;
                        nBytes += _resolutionInfo.Read(io, handle);
                        break;

                        // DisplayInfo structure
                    case 1007:
                        _bDisplayInfoFilled = true;
                        nBytes += _displayInfo.Read(io, handle);
                        break;

                        // (Photoshop 4.0) Copyright flag
                        // Boolean indicating whether image is copyrighted. Can be set via Property suite or by user in File Info...
                    case 1034:
                        n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
                        nBytes += n * sizeof(ShortValue);
                        _bCopyright = (1 == psdGetValue(ShortValue, sizeof(ShortValue)));
                        break;

                        // (Photoshop 4.0) Thumbnail resource for Photoshop 4.0 only
                    case 1033:
                        // (Photoshop 5.0) Thumbnail resource (supersedes resource 1033)
                    case 1036:
                        {
                            _bThumbnailFilled = true;
                            bool bBGR = (1033==oResource._ID);
                            nBytes += _thumbnail.Read(io, handle, oResource._Size, bBGR);
                            break;
                        }

                        // (Photoshop 5.0) Global Angle
                        // 4 bytes that contain an integer between 0 and 359, which is the global
                        // lighting angle for effects layer. If not present, assumed to be 30.
                    case 1037:
                        n = (int)io->read_proc(&IntValue, sizeof(IntValue), 1, handle);
                        nBytes += n * sizeof(IntValue);
                        _GlobalAngle = psdGetValue(IntValue, sizeof(_GlobalAngle) );
                        break;

                        // ICC profile
                    case 1039:
                        nBytes += _iccProfile.Read(io, handle, oResource._Size);
                        break;

                        // (Photoshop 6.0) Indexed Color Table Count
                        // 2 bytes for the number of colors in table that are actually defined
                    case 1046:
                        n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
                        nBytes += n * sizeof(ShortValue);
                        _ColourCount = (short)psdGetValue(ShortValue, sizeof(ShortValue) );
                        break;

                        // (Photoshop 6.0) Transparency Index.
                        // 2 bytes for the index of transparent color, if any.
                    case 1047:
                        n = (int)io->read_proc(&ShortValue, sizeof(ShortValue), 1, handle);
                        nBytes += n * sizeof(ShortValue);
                        _TransparentIndex = (short)psdGetValue(ShortValue, sizeof(ShortValue) );
                        break;

                    default:
                        {
                            // skip resource
                            unsigned skip_length = MIN(oResource._Size, nTotalBytes - nBytes);
                            io->seek_proc(handle, skip_length, SEEK_CUR);
                            nBytes += skip_length;
                        }
                        break;
                }
            }
        }
    }

    if (nBytes == nTotalBytes) {
        bSuccess = true;
    }

    return bSuccess;

}

FIBITMAP* psdParser::ReadImageData(FreeImageIO *io, fi_handle handle) {
    if(handle == NULL)
        return NULL;

    bool header_only = (_fi_flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;

    WORD nCompression = 0;
    io->read_proc(&nCompression, sizeof(nCompression), 1, handle);

#ifndef FREEIMAGE_BIGENDIAN
    SwapShort(&nCompression);
#endif

    if((nCompression != PSDP_COMPRESSION_NONE && nCompression != PSDP_COMPRESSION_RLE)) {
        FreeImage_OutputMessageProc(_fi_format_id, "Unsupported compression %d", nCompression);
        return NULL;
    }

    const unsigned nWidth = _headerInfo._Width;
    const unsigned nHeight = _headerInfo._Height;
    const unsigned nChannels = _headerInfo._Channels;
    const unsigned depth = _headerInfo._BitsPerChannel;
    const unsigned bytes = (depth == 1) ? 1 : depth / 8;

    // channel(plane) line (BYTE aligned)
    const unsigned lineSize = (_headerInfo._BitsPerChannel == 1) ? (nWidth + 7) / 8 : nWidth * bytes;

    if(nCompression == PSDP_COMPRESSION_RLE && depth > 16) {
        FreeImage_OutputMessageProc(_fi_format_id, "Unsupported RLE with depth %d", depth);
        return NULL;
    }

    // build output buffer

    FIBITMAP* bitmap = NULL;
    unsigned dstCh = 0;

    short mode = _headerInfo._ColourMode;

    if(mode == PSDP_MULTICHANNEL && nChannels < 3) {
        // CM
        mode = PSDP_GRAYSCALE; // C as gray, M as extra channel
    }

    bool needPalette = false;
    switch (mode) {
        case PSDP_BITMAP:
        case PSDP_DUOTONE:
        case PSDP_INDEXED:
        case PSDP_GRAYSCALE:
            dstCh = 1;
            switch(depth) {
                case 16:
                    bitmap = FreeImage_AllocateHeaderT(header_only, FIT_UINT16, nWidth, nHeight, depth*dstCh);
                    break;
                case 32:
                    bitmap = FreeImage_AllocateHeaderT(header_only, FIT_FLOAT, nWidth, nHeight, depth*dstCh);
                    break;
                default: // 1-, 8-
                    needPalette = true;
                    bitmap = FreeImage_AllocateHeader(header_only, nWidth, nHeight, depth*dstCh);
                    break;
            }
            break;
        case PSDP_RGB:
        case PSDP_LAB:
        case PSDP_CMYK  :
        case PSDP_MULTICHANNEL  :
            // force PSDP_MULTICHANNEL CMY as CMYK
            dstCh = (mode == PSDP_MULTICHANNEL && !header_only) ? 4 : MIN<unsigned>(nChannels, 4);
            if(dstCh < 3) {
                throw "Invalid number of channels";
            }

            switch(depth) {
                case 16:
                    bitmap = FreeImage_AllocateHeaderT(header_only, dstCh < 4 ? FIT_RGB16 : FIT_RGBA16, nWidth, nHeight, depth*dstCh);
                    break;
                case 32:
                    bitmap = FreeImage_AllocateHeaderT(header_only, dstCh < 4 ? FIT_RGBF : FIT_RGBAF, nWidth, nHeight, depth*dstCh);
                    break;
                default:
                    bitmap = FreeImage_AllocateHeader(header_only, nWidth, nHeight, depth*dstCh);
                    break;
            }
            break;
        default:
            throw "Unsupported color mode";
            break;
    }
    if(!bitmap) {
        throw FI_MSG_ERROR_DIB_MEMORY;
    }

    // write thumbnail
    FreeImage_SetThumbnail(bitmap, _thumbnail.getDib());

    // @todo Add some metadata model

    if(header_only) {
        return bitmap;
    }

    // Load pixels data

    const unsigned dstChannels = dstCh;

    const unsigned dstBpp =  (depth == 1) ? 1 : FreeImage_GetBPP(bitmap)/8;
    const unsigned dstLineSize = FreeImage_GetPitch(bitmap);
    BYTE* const dst_first_line = FreeImage_GetScanLine(bitmap, nHeight - 1);//<*** flipped

    BYTE* line_start = new BYTE[lineSize]; //< fileline cache

    switch ( nCompression ) {
        case PSDP_COMPRESSION_NONE: // raw data
            {
                for(unsigned c = 0; c < nChannels; c++) {
                    if(c >= dstChannels) {
                        // @todo write extra channels
                        break;
                    }

                    const unsigned channelOffset = c * bytes;

                    BYTE* dst_line_start = dst_first_line;
                    for(unsigned h = 0; h < nHeight; ++h, dst_line_start -= dstLineSize) {//<*** flipped

                        io->read_proc(line_start, lineSize, 1, handle);

                        for (BYTE *line = line_start, *dst_line = dst_line_start; line < line_start + lineSize;
                             line += bytes, dst_line += dstBpp) {
#ifdef FREEIMAGE_BIGENDIAN
                            memcpy(dst_line + channelOffset, line, bytes);
#else
                            // reverse copy bytes
                            for (unsigned b = 0; b < bytes; ++b) {
                                dst_line[channelOffset + b] = line[(bytes-1) - b];
                            }
#endif // FREEIMAGE_BIGENDIAN
                        }
                    } //< h
                }//< ch

                SAFE_DELETE_ARRAY(line_start);

            }
            break;

        case PSDP_COMPRESSION_RLE: // RLE compression
            {

                // The RLE-compressed data is preceeded by a 2-byte line size for each row in the data,
                // store an array of these

                // later use this array as WORD rleLineSizeList[nChannels][nHeight];
                WORD *rleLineSizeList = new (std::nothrow) WORD[nChannels*nHeight];

                if(!rleLineSizeList) {
                    FreeImage_Unload(bitmap);
                    SAFE_DELETE_ARRAY(line_start);
                    throw std::bad_alloc();
                }

                io->read_proc(rleLineSizeList, 2, nChannels * nHeight, handle);

                WORD largestRLELine = 0;
                for(unsigned ch = 0; ch < nChannels; ++ch) {
                    for(unsigned h = 0; h < nHeight; ++h) {
                        const unsigned index = ch * nHeight + h;

#ifndef FREEIMAGE_BIGENDIAN
                        SwapShort(&rleLineSizeList[index]);
#endif
                        if(largestRLELine < rleLineSizeList[index]) {
                            largestRLELine = rleLineSizeList[index];
                        }
                    }
                }

                BYTE* rle_line_start = new (std::nothrow) BYTE[largestRLELine];
                if(!rle_line_start) {
                    FreeImage_Unload(bitmap);
                    SAFE_DELETE_ARRAY(line_start);
                    SAFE_DELETE_ARRAY(rleLineSizeList);
                    throw std::bad_alloc();
                }

                // Read the RLE data (assume 8-bit)

                const BYTE* const line_end = line_start + lineSize;

                for (unsigned ch = 0; ch < nChannels; ch++) {
                    const unsigned channelOffset = ch * bytes;

                    BYTE* dst_line_start = dst_first_line;
                    for(unsigned h = 0; h < nHeight; ++h, dst_line_start -= dstLineSize) {//<*** flipped
                        const unsigned index = ch * nHeight + h;

                        // - read and uncompress line -

                        const WORD rleLineSize = rleLineSizeList[index];

                        io->read_proc(rle_line_start, rleLineSize, 1, handle);

                        for (BYTE* rle_line = rle_line_start, *line = line_start;
                             rle_line < rle_line_start + rleLineSize, line < line_end;) {

                            int len = *rle_line++;

                            // NOTE len is signed byte in PackBits RLE

                            if ( len < 128 ) { //<- MSB is not set
                                // uncompressed packet

                                // (len + 1) bytes of data are copied

                                ++len;

                                // assert we don't write beyound eol
                                memcpy(line, rle_line, line + len > line_end ? line_end - line : len);
                                line += len;
                                rle_line += len;
                            }
                            else if ( len > 128 ) { //< MSB is set

                                // RLE compressed packet

                                // One byte of data is repeated (–len + 1) times

                                len ^= 0xFF; // same as (-len + 1) & 0xFF
                                len += 2;    //

                                // assert we don't write beyound eol
                                memset(line, *rle_line++, line + len > line_end ? line_end - line : len);
                                line += len;

                            }
                            else if ( 128 == len ) {
                                // Do nothing
                            }
                        }//< rle_line

                        // - write line to destination -

                        if(ch >= dstChannels) {
                            // @todo write to extra channels
                            break;
                        }

                        // byte by byte copy a single channel to pixel
                        for (BYTE *line = line_start, *dst_line = dst_line_start; line < line_start + lineSize;
                             line += bytes, dst_line += dstBpp) {

#ifdef FREEIMAGE_BIGENDIAN
                            memcpy(dst_line + channelOffset, line, bytes);
#else
                            // reverse copy bytes
                            for (unsigned b = 0; b < bytes; ++b) {
                                dst_line[channelOffset + b] = line[(bytes-1) - b];
                            }
#endif // FREEIMAGE_BIGENDIAN
                        }
                    }//< h
                }//< ch

                SAFE_DELETE_ARRAY(line_start);
                SAFE_DELETE_ARRAY(rleLineSizeList);
                SAFE_DELETE_ARRAY(rle_line_start);
            }
            break;

        case 2: // ZIP without prediction, no specification
            break;

        case 3: // ZIP with prediction, no specification
            break;

        default: // Unknown format
            break;

    }

    // --- Further process the bitmap ---

    if((mode == PSDP_CMYK || mode == PSDP_MULTICHANNEL)) {
        // CMYK values are "inverted", invert them back

        if(mode == PSDP_MULTICHANNEL) {
            invertColor(bitmap);
        } else {
            FreeImage_Invert(bitmap);
        }

        if((_fi_flags & PSD_CMYK) == PSD_CMYK) {
            // keep as CMYK

            if(mode == PSDP_MULTICHANNEL) {
                //### we force CMY to be CMYK, but CMY has no ICC.
                // Create empty profile and add the flag.
                FreeImage_CreateICCProfile(bitmap, NULL, 0);
                FreeImage_GetICCProfile(bitmap)->flags |= FIICC_COLOR_IS_CMYK;
            }
        }
        else {
            // convert to RGB

            ConvertCMYKtoRGBA(bitmap);

            // The ICC Profile is no longer valid
            _iccProfile.clear();

            // remove the pending A if not present in source
            if(nChannels == 4 || nChannels == 3 ) {
                FIBITMAP* t = RemoveAlphaChannel(bitmap);
                if(t) {
                    FreeImage_Unload(bitmap);
                    bitmap = t;
                } // else: silently fail
            }
        }
    }
    else if ( mode == PSDP_LAB && !((_fi_flags & PSD_LAB) == PSD_LAB)) {
        ConvertLABtoRGB(bitmap);
    }
    else {
        if (needPalette && FreeImage_GetPalette(bitmap)) {

            if(mode == PSDP_BITMAP) {
                CREATE_GREYSCALE_PALETTE_REVERSE(FreeImage_GetPalette(bitmap), 2);
            }
            else if(mode == PSDP_INDEXED) {
                if(!_colourModeData._plColourData || _colourModeData._Length != 768 || _ColourCount < 0) {
                    FreeImage_OutputMessageProc(_fi_format_id, "Indexed image has no palette. Using the default grayscale one.");
                } else {
                    _colourModeData.FillPalette(bitmap);
                }
            }
            // GRAYSCALE, DUOTONE - use default grayscale palette
        }

#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
        if(FreeImage_GetImageType(bitmap) == FIT_BITMAP) {
            SwapRedBlue32(bitmap);
        }
#endif
    }

    return bitmap;
}

FIBITMAP* psdParser::Load(FreeImageIO *io, fi_handle handle, int s_format_id, int flags) {
    FIBITMAP *Bitmap = NULL;

    _fi_flags = flags;
    _fi_format_id = s_format_id;

    try {
        if (NULL == handle) {
            throw("Cannot open file");
        }

        if (!_headerInfo.Read(io, handle)) {
            throw("Error in header");
        }

        if (!_colourModeData.Read(io, handle)) {
            throw("Error in ColourMode Data");
        }

        if (!ReadImageResources(io, handle)) {
            throw("Error in Image Resource");
        }

        if (!ReadLayerAndMaskInfoSection(io, handle)) {
            throw("Error in Mask Info");
        }

        Bitmap = ReadImageData(io, handle);
        if (NULL == Bitmap) {
            throw("Error in Image Data");
        }

        // set resolution info
        if(NULL != Bitmap) {
            unsigned res_x = 2835;  // 72 dpi
            unsigned res_y = 2835;  // 72 dpi
            if (_bResolutionInfoFilled) {
                _resolutionInfo.GetResolutionInfo(res_x, res_y);
            }
            FreeImage_SetDotsPerMeterX(Bitmap, res_x);
            FreeImage_SetDotsPerMeterY(Bitmap, res_y);
        }

        // set ICC profile
        FreeImage_CreateICCProfile(Bitmap, _iccProfile._ProfileData, _iccProfile._ProfileSize);
        if ((flags & PSD_CMYK) == PSD_CMYK) {
            short mode = _headerInfo._ColourMode;
            if((mode == PSDP_CMYK) || (mode == PSDP_MULTICHANNEL)) {
                FreeImage_GetICCProfile(Bitmap)->flags |= FIICC_COLOR_IS_CMYK;
            }
        }

    } catch(const char *text) {
        FreeImage_OutputMessageProc(s_format_id, text);
    }
    catch(const std::exception& e) {
        FreeImage_OutputMessageProc(s_format_id, "%s", e.what());
    }

    return Bitmap;
}
